#include <iostream>
#include <stdlib.h>
#include <gmpxx.h>
#include <mpi.h>
#include <fstream>
#include <sys/time.h>
#include <time.h>


#include "Polynome.hpp"
#include "Erathostene.hpp"
#include "Factor.hpp"
#include "Matrice.hpp"

#include "CribleMPI.hpp"
#include "Crible.hpp"

#include "Paire.hpp"
using namespace std;

void generateFile(mpz_class & n, Factor & RFB, Factor & AFB, Factor & QCB, mpz_class & sievingBound)
{
   ofstream fic;

   fic.open("notregnfs", ios::out);
   
   if(fic.bad())
   {
      exit(-1);
   }
   
   // Nombre a factoriser
   fic << n << endl;
   
   fic << 1 << endl;
   
   fic << 1000 << endl;
   // borne rfb
   fic << RFB.getPaire(RFB.getTaille() - 1).getPaire(0) << endl;
   // borne afb
   fic << AFB.getPaire(AFB.getTaille() - 1).getPaire(0) << endl;
   // nombre de quadratic character
   fic << QCB.getTaille() << endl;
   // Fichier contenant les nombres premiers
   fic << "nombresPremiers" << endl;
   // Borne du sievingBound
   fic << sievingBound << endl;
   
   fic.close();
}


int main (int argc, char ** argv)
{	
   //Notre nombre
   char nb[300];
   int  nbChiffres, rank;
   int  indiceNbPremier = 0;
   int nbB;
   
   
   //Donnes utiles pour le polynome
   strcpy(nb, argv[1]);
   nbChiffres = strlen(nb);
   
   
   //Bornes des diferentes bases
   int  nbPairesRFB;
   int  nbPairesAFB;
   int  nbPairesQCB;
   
   sscanf(argv[2], "%d", &nbPairesRFB);
   
   nbPairesAFB = (int) 2.5 * nbPairesRFB;
   nbPairesQCB = (int) nbPairesRFB/10;
   
   //Donnees relative au sieving
   mpz_class upBound(argv[3]);
   mpz_class lowBound = upBound * (-1);
   
   sscanf(argv[4], "%d", &nbB);
   
   // Nombres premiers jusqu'à 3000000
   int borneErathostene = 3000000;

   //Variables utiles pour le calcul du temps
   struct timeval tempsDepart;
   struct timeval tempsFinal;

   

   
   //Initialisation du Nombre
   mpz_class         nombre(nb);

   Erathostene       cribleErathostene(borneErathostene);

   // Declaration du polynome
   Polynome          n(nb, nbChiffres);
   
   
   //Initialisation de MPI
   MPI_Init(&argc, &argv);
   
   

   // Possibles big Numbers
   //divisors of 1288005205276048901 is: 2971215073 433494437
   //divisors of 119250351863011661 is: 8244555817 14464133
   //divisors of 25994451173965601 is: 279235349 93091549


   

   Factor rationnalFactorBase(nbPairesRFB);
   Factor rationnalAlgebraicBase(nbPairesAFB);
   Factor quadraticCharacterBase(nbPairesQCB);
      
   // Tableau contenant les nombres premiers
   mpz_class * tabNbPremier = cribleErathostene.execution();
   
   Factor    * ab;
   
   
   MPI_Comm_rank(MPI_COMM_WORLD, &rank);
   
      
      
      
   // Recherche des coeficients polynome et de _m   
   
   gettimeofday(&tempsDepart, NULL);
   
   n.recherchePoly(nombre);
   
   gettimeofday(&tempsFinal, NULL);
   
   if(!rank)
   {
      cout << endl << "Polynome trouve en : " << tempsFinal.tv_sec - tempsDepart.tv_sec << " sec et "; 
      cout << tempsFinal.tv_usec - tempsDepart.tv_usec << "µs" << endl << endl << endl;
   }
   
      
   // Creation des bases
   
   
   gettimeofday(&tempsDepart, NULL);
   
   rationnalFactorBase.rationnalBase(&tabNbPremier , n.getM());
   if(!rank) cout << "Rationnal Factor base trouvee" << endl;
   indiceNbPremier = rationnalAlgebraicBase.algebraicBase(&tabNbPremier, borneErathostene, n);
   if(!rank) cout << "Algebraic factor base trouvee" << endl;
   quadraticCharacterBase.quadraticCharacterBase(&tabNbPremier, indiceNbPremier, borneErathostene, n);
   if(!rank) cout << "Quadratic Factor base trouvee" << endl << endl ;
   
   gettimeofday(&tempsFinal, NULL);
   
   
   
   if(!rank)
   {
      cout << "bases trouvees en : "  << tempsFinal.tv_sec - tempsDepart.tv_sec << " sec et "; 
      cout << tempsFinal.tv_usec - tempsDepart.tv_usec << "µs" << endl << endl << endl;
   }

   

   // Sieving
   
   CribleMPI crible(&rationnalFactorBase, &rationnalAlgebraicBase, nombre, n.getM());
   //   Crible crible(&rationnalFactorBase, &rationnalAlgebraicBase, nombre, n.getM());
      


         
   gettimeofday(&tempsDepart, NULL);
      
   //  ab = crible.sieving(lowBound, upBound, quadraticCharacterBase.getTaille(), cribleErathostene, n);
   ab = crible.sieving(lowBound, upBound, nbPairesQCB, cribleErathostene, n, 1000); 
            
   gettimeofday(&tempsFinal, NULL);
   
   if(!rank)
   {
      cout << "Sieving termine en : " << tempsFinal.tv_sec - tempsDepart.tv_sec << " sec et "; 
      cout << tempsFinal.tv_usec - tempsDepart.tv_usec << "µs" << endl;
   }
   else
   {
      if (ab) delete ab;
      n.~Polynome();
      rationnalAlgebraicBase.~Factor();
      rationnalFactorBase.~Factor();
      quadraticCharacterBase.~Factor();
      cribleErathostene.~Erathostene();

      MPI_Finalize();
   }

   if(!rank)
   {
      
      
      gettimeofday(&tempsDepart, NULL);

      
      
      // Creation de la matrice
      Matrice  mat(ab->getTaille(), rationnalAlgebraicBase.getTaille() + rationnalFactorBase.getTaille() + quadraticCharacterBase.getTaille() + 1);
   
      // Remplissage de la matrice
      mat.buildMatrix(rationnalFactorBase, rationnalAlgebraicBase, quadraticCharacterBase, *ab, n);
      
      
      cout << "matrice construite" << endl;
   
      // Transposee de la matrice
      Matrice           mTrans                        = mat.trans();
      
      // Algebre lineaire et creation du vecteur contenant les dependances
      mTrans.Gauss();
      
      
      cout << "Elimination terminee" << endl;
   
   
      gettimeofday(&tempsFinal, NULL);
   
      cout << "Matrice termine en : " << tempsFinal.tv_sec - tempsDepart.tv_sec << " sec et "; 
      cout << tempsFinal.tv_usec - tempsDepart.tv_usec << "µs" << endl;
      
      
      
      // Generation des fichiers
      // Generation des fichiers contenant les nombres premiers
      
      
      cribleErathostene.generateFile();
      
      // Generation de "gnfs"
      generateFile(nombre, rationnalFactorBase, rationnalAlgebraicBase, quadraticCharacterBase, upBound);
      
      // Generation de "gnfs.poly"
      n.generateFile();
      
      
      // Generation des "gnfs.*" pour les bases
      Factor::generateFile(rationnalAlgebraicBase, rationnalFactorBase, quadraticCharacterBase, *ab);
      
      // Generation de "gnfs.mat"
      mTrans.generateFile();
      
      cout << "fichier cree" << endl;
      
      delete ab;
      n.~Polynome();
      rationnalAlgebraicBase.~Factor();
      rationnalFactorBase.~Factor();
      quadraticCharacterBase.~Factor();
      cribleErathostene.~Erathostene();
      mat.~Matrice();
      mTrans.~Matrice();
      
      
      MPI_Abort(MPI_COMM_WORLD, 0);
   }

   return 0;
}
